<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>0011_圆弧形可拖拽进度条(限制到360度后不同再移动)</title>
</head>
<body>
  <canvas id="canvas" width="600" height="600"></canvas>
</body>
<script>
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  
  // 度数转弧度
  function numToRadian (num) {
    if (!num) {
      return 0
    }
    return (Math.PI / 180) * num
  }

  // 弧度转度数
  function radianToNum (radian) {
    if (!radian) {
      return 0
    }
    return radian * 180 / Math.PI
  }

  // 绘制外层圆环
  function drawOuterRing() {
    ctx.beginPath();
    ctx.arc(300, 300, 140, numToRadian(-90), numToRadian(270), false); // false 顺时针 true 是逆时针
    ctx.strokeStyle = '#F8FBFF';
    ctx.lineWidth = 25;
    // ctx.lineCap = 'round';
    ctx.stroke(); // 通过填充路径绘制实心图形
    ctx.closePath();
  }

  function getOuterPointXY(angle, R) {
    var point = {}
    point.x = R * Math.cos(angle);
    point.y=  R * Math.sin(angle);
    point = respotchange(point)
    return point
}

  // 绘制内层底部圆环
  function drawGreyRing () {
    ctx.beginPath();
    ctx.arc(300, 300, 100, numToRadian(-90), numToRadian(270), false); // false 顺时针 true 是逆时针
    ctx.strokeStyle = '#dddddd';
    ctx.lineWidth = 40;
    // ctx.lineCap = 'round';
    ctx.stroke(); // 通过填充路径绘制实心图形
    ctx.closePath();
  }
  
  // 绘制内层上方蓝色圆环
  function drawBlueRing (end, start) {
    if (start === undefined) {
      start = -90
    }

    if (end === undefined) {
      end = -90
    }
    ctx.beginPath();
    ctx.lineWidth = 40;
    ctx.strokeStyle = 'blue';
    ctx.arc(300, 300, 100, numToRadian(start), numToRadian(end), false); 
    ctx.stroke(); // 通过填充路径绘制实心图形
    ctx.closePath();
  }

// 绘制滑动点
// 记录滑动点原心的位置
var sliderPointX = 300, sliderPointY = 200;
  function drawSliderPoint (x, y) {
    if (x === undefined) {
      x = 300
    }
    if (y === undefined) {
      y = 200
    }
    ctx.beginPath();
    ctx.fillStyle = 'red';
    ctx.arc(x, y, 20, 0, numToRadian(360), false); 
    ctx.fill(); // 通过填充路径绘制实心图形3
    ctx.closePath();
    sliderPointX = x; // 记录滑动点原心的位置
    sliderPointY = y;
  }

  drawOuterRing()
  drawGreyRing()
  drawBlueRing()
  drawSliderPoint()
  
  // 分成n分
  // 最后一个坐标是90   直角坐标系
  // 假如分成3份
  // 90 = 90 + 120 * 0 // 第三个点 
  // 210 = 90 + 120 * 1 // 第二个点
  // 60 = 90 + 120 * 2  // 第一个点 大于 360 减去360

  // 分割圆环，得到角度
  function getSplitRingAngle (n) {
    let angles = []
    if (!n) {
      return 0
    }
    let part = 360 / n
    for (let i = 0; i < n; i ++) {
      let angle = 90 + part * i
      angle = angle > 360 ? angle - 360 : angle
      angles.push(angle)
    }
    return angles
  }

  // 获取外层绘制文字和指针的起始点
  // 抽象为一个圆被分割成如下angles，在半径R处的坐标位置
  function getOuterPointsXY (angles, R) {
    let points = []
    if (!angles || angles.length === 0) {
      return []
    }
    let len = angles.length
    for (let index = 0; index < len; index++) {
      let point= null
      const angle = angles[index];
      point = getOuterPointXY(numToRadian(angle), R)
      points.push(point)
    }
    return points
  }

  /**绘制外层圆环中的指针
    * @param x, y 起始坐标
  */
  function drawPeriodPointer (x, y, toX, toY) {
    ctx.beginPath();
    ctx.moveTo(x,y);
    ctx.lineWidth = 4;
    ctx.lineTo(toX,toY);
    ctx.stroke();
    ctx.closePath();
  }

  function drawPeriodText (x, y, text, angle) {
    ctx.save()
    ctx.beginPath();
    ctx.textAlign = 'center' // 文字居中
    ctx.translate(x, y) // canvas
    let rotateAngle = 0

    if (angle >=0 && angle < 90) { // 将文本的中垂线 旋转 和半径重合  中垂线和（圆心和文本位置x,y连线）的夹角
      rotateAngle = -(90 + angle)
    }

    if (angle > 90 && angle < 180) {
      rotateAngle = 270 - angle
    }
    if ( angle >= 180 && angle <= 270) {
      rotateAngle = 90 - (angle - 180)
    }

    if ( angle > 270 && angle <= 360) {
      rotateAngle = 270 - angle
    }
    ctx.rotate(numToRadian(rotateAngle))
    ctx.fillText(text, 0, 0);
    ctx.restore();
    ctx.closePath();
  }

  function getPointerLineXY (startPonits, endPoints) {
    if (!startPonits || startPonits.length === 0 ) {
      return []
    }

    if (!endPoints || endPoints.length === 0 ) {
      return []
    }

    if (startPonits.length !== endPoints.length) {
      return []
    }

    let len = startPonits.length
    let points = []
    for (let index = 0; index < len; index++) {
      let point = {}
      point.x = startPonits[index].x
      point.y = startPonits[index].y
      point.toX = endPoints[index].x
      point.toY = endPoints[index].y
      points.push(point)
    }
    return points
  }

  var splitAngles = getSplitRingAngle(8)
  var splitRingPoints = getOuterPointsXY(splitAngles, 170) // 152
  var splitRingEndPoints = getOuterPointsXY(splitAngles, 135)
  var splitRingTextPoings = getOuterPointsXY(splitAngles, 170)
  var pointerLinePoints = getPointerLineXY(splitRingPoints, splitRingEndPoints)
  // drawPeriodPointer(300, 160, 300, 180)
  pointerLinePoints.forEach((point, index) => {
    console.log(point)
    drawPeriodPointer(point.x, point.y, point.toX, point.toY)
  })

  splitRingTextPoings.forEach((point, index) => {
    console.log(splitAngles[index])
    drawPeriodText(point.x, point.y, '3期', splitAngles[index])
  })
  // console.log(splitRingPoints)
  // 根据度数画第一个点
  // console.log(getOuterPointXY(numToRadian(90)))
  // drawSliderPoint(splitRingPoints)
  // drawSliderPoint(300, 160)
  // console.log(getOuterPointXY(numToRadian(210)))
  // console.log(getOuterPointXY(numToRadian(330)))
  function getX (ev) { // 获取鼠标在canvas内坐标x
    return ev.clientX - canvas.getBoundingClientRect().left
  }

  function getY (ev) { // 获取鼠标在canvas内坐标y
  return ev.clientY - canvas.getBoundingClientRect().top
  }

    // 以canvas中心点为原心的坐标系(简称 中心坐标)
    function spotchange (a){ //屏幕坐标转化为中心坐标 200 200 是canvas中心点坐标  原代码没有考虑为0的情况
    var target={};
    if(a.x<300 && a.y<300){　　　　　　//二象限
        target.x=-(300-a.x); 
        target.y=300-a.y; 
    }else if(a.x>300 && a.y<300){　　//一象限  
        target.x=a.x-300; 
        target.y=300-a.y; 
    }else if(a.x>300 && a.y>300){　　//四象限
        target.x=a.x-300;
        target.y=-(a.y-300) 
    }else if(a.x<300 && a.y>300){　　//三象限
        target.x=-(300-a.x); 
        target.y=-(a.y-300); 
    }
    if (a.y === 300) {
      target.y = 0
      target.x = a.x > 0 ? a.x - 300 : -(300-a.x)
    }

    if (a.x === 300) {
      target.x = 0
      target.y = a.y > 0 ? -(a.y-300) : 300-a.y
    }
    if (target.x === undefined || target.y === undefined) {
      console.error('坐标处理异常')
    }
    return target; 
}

// 中心坐标（红色坐标）->屏幕坐标（黑色坐标）

function respotchange(a){ //中心坐标转化为屏幕坐标
    var target={};
    if(a.x>0 && a.y>0){
      target.x=300+a.x;
      target.y=(300-a.y);
    }else if(a.x<0 && a.y>0){
      target.x=300+a.x;
      target.y=300-a.y;
    }else if(a.x<0 && a.y<0){
      target.x=300+a.x;
      target.y=-(a.y-300)
    }else if(a.x>0 && a.y<0){
      target.x=300+a.x;
      target.y=-(a.y-300);
    }
    if (a.y == 0) {
      target.x= 300+a.x 
      target.y = 300
    }

    if (a.x == 0) {
      target.x = 300
      target.y = a.y > 0 ? 300-a.y : -(a.y-300)
    }
    if (target.x === undefined || target.y === undefined) {
      console.error('坐标处理异常')
    }
    return target;
  }


  var onCircularRing = false
  var onSlider = false
  function OnMouseDown(evt){
    var X=getX(evt);    //获取当前鼠标位置横坐标
    var Y=getY(evt);     //获取当前鼠标位置纵坐标
    // 距圆心的距离
    var dis = Math.pow(Math.pow(X - 300, 2) + Math.pow(Y - 300, 2), 0.5)
    var disSlider = Math.pow(Math.pow(X - sliderPointX, 2) + Math.pow(Y - sliderPointY, 2), 0.5)
    onCircularRing = (dis >= 80) && (dis <= 120)
    onSlider = disSlider <= 20
}

function getRadian(lx,ly) {
  var tem={};    //存放目标坐标位置
  if (lx == 0) {
    tem.o = ly > 0 ? Math.PI / 2 : 3 * Math.PI / 2 // 90或180度
    tem.z = ly > 0 ? -Math.PI / 2 : Math.PI / 2
  } else {
    tem.o=Math.atan(ly/lx); // 鼠标移动点圆形角
  }
  tem.x = 100 *Math.cos(tem.o);
  tem.y= 100*Math.sin(tem.o);
  if(lx<0){ //坐标点处理（正负）
    tem.x=-tem.x;
    tem.y=-tem.y;
  }
  // 这里转换成canvas真正的绘图弧度  
  // 中心坐标 90 -90  对应  屏幕canvas -90~90
  if (lx> 0) {
    tem.z = - tem.o
  } else if(lx < 0) {
    tem.z = - tem.o +  Math.PI
  } 


  // if(lx>0){  //弧度值处理
  //     tem.z=-Math.atan(tem.y/tem.x)+Math.PI*2;
  //   }else{
  //     tem.z=-Math.atan(tem.y/tem.x)+Math.PI;
  //   }
  return tem
}
var currValue = 0
function onMouseMove (evt) { // 拖拽 // 这里要做节流
  if (onCircularRing && onSlider) {
    var X=getX(evt);    //获取当前鼠标位置横坐标
    var Y=getY(evt);     //获取当前鼠标位置纵坐标
    // console.log('判断鼠标是否在圆环上', onCircularRing) // 判断鼠标是否在圆环上
    // console.log('判断是不是在滑点上', onSlider)
    // 判断是不是在滑点上

    // 屏幕坐标转中心坐标
    var screenPoint = spotchange({x:X, y:Y})
    // console.log('中心坐标', screenPoint)
    var silderCP = getRadian(screenPoint.x, screenPoint.y) // CP -> 中心坐标
    // console.log('滑动点中心坐标', silderCP) // 中心坐标
    // 中心坐标转屏幕坐标
    var silderSP = respotchange({ x:silderCP.x, y:silderCP.y })// SP -> 屏幕坐标
    // console.log('滑动点屏幕坐标', silderSP) //

    
    var sliderAngle = radianToNum(silderCP.z)
    var val = (90 + sliderAngle) / 360 * 100
    if (val >= 100) {
      val = 100
    }
    if (val <= 0) {
      val = 0
    }

    if (Math.abs(val - currValue) > 10) {
      return
    }

    if (currValue != Math.round(val)) {
      currValue = Math.round(val)
    }
    ctx.clearRect(0, 0, 600, 600)
      drawGreyRing()
      drawBlueRing(sliderAngle)
      drawSliderPoint(silderSP.x, silderSP.y)
  }
}

function onMouseUp () { // 鼠标抬起释放
  onCircularRing = false
  onSlider = false
}

  function addEvent () {
    canvas.addEventListener("mousedown", OnMouseDown.bind(canvas))
    canvas.addEventListener("mousemove", onMouseMove.bind(canvas))
    canvas.addEventListener("mouseup", onMouseUp.bind(canvas))
  }
  addEvent()
</script>
</html>